﻿#region usings

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

#endregion

namespace Mp3Observer.Common
{
    /// <summary>
    /// This class provides helpung function for UI access (background worker, VisualTreeHelp, ItemContainerGenerator,..)
    /// </summary>
    public static class VisualHelper
    {
        /// <summary>
        /// the found frameworkElement
        /// </summary>
        private static FrameworkElement frameworkElement;

        /// <summary>
        /// list of hitted framework elements of the visual tree search
        /// </summary>
        private static List<FrameworkElement> hittedFrameworkElements;

        /// <summary>
        /// the type of the element we look for.
        /// </summary>
        private static Type searchedElementType;

        /// <summary>
        /// Invokes an action with the dispatcher.
        /// </summary>
        /// <param name="dispatcher">The dispatcher.</param>
        /// <param name="action">The action.</param>
        public static void InvokeAction(Dispatcher dispatcher, Action action)
        {
            dispatcher.Invoke(action, DispatcherPriority.Normal);
        }

        /// <summary>
        /// Starts a backgroundworker.
        /// </summary>
        /// <param name="doWork">The do work.</param>
        /// <param name="runWorkerCompleted">The run worker completed.</param>
        /// <param name="argument">The argument.</param>
        /// <param name="supportsCancellation">if set to <c>true</c> [supports cancellation].</param>
        public static void DoBackgroundWork(DoWorkEventHandler doWork, RunWorkerCompletedEventHandler runWorkerCompleted, object argument, bool supportsCancellation = false)
        {
            var bw = new BackgroundWorker();
            bw.DoWork += doWork;
            bw.RunWorkerCompleted += runWorkerCompleted;
            bw.WorkerSupportsCancellation = supportsCancellation;
            bw.RunWorkerAsync(argument);
        }

        /// <summary>
        /// Waits for the func and executes the action.
        /// TODO : schönere Lösung mit AutoResetEvent ??
        /// </summary>
        /// <param name="predicate">The predicate.</param>
        /// <param name="action">The action.</param>
        /// <param name="milliseconds">The milliseconds.</param>
        public static void WaitForAndAction(Func<bool> predicate, Action action, double milliseconds)
        {
            //ManualResetEvent manualEvent = new ManualResetEvent(true);
            //Thread t = new Thread(new ThreadStart(() =>
            //{
            //    if (predicate())
            //    {
            //        action();
            //        manualEvent.Set();
            //    }
            //}));
            //t.Priority = ThreadPriority.BelowNormal;
            //t.Start();
            //manualEvent.WaitOne();


            if (predicate())
            {
                action();
                return;
            }

            var dt = new DispatcherTimer();
            dt.Tick += (sender, e) =>
            {
                if (predicate())
                {
                    dt.Stop();
                    action();
                }
            };
            dt.Interval = TimeSpan.FromMilliseconds(milliseconds);
            dt.Start();
        }

        /// <summary>
        /// Focus in a callback to run on another thread, ensuring the main UI thread is initialized by the time focus is set
        /// </summary>
        /// <param name="element">The element.</param>
        public static void Focus(UIElement element)
        {
            ThreadPool.QueueUserWorkItem(delegate(object theElement)
            {
                UIElement elem = (UIElement)theElement;
                elem.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
                {
                    elem.Focus();
                    Keyboard.Focus(elem);
                }));
            }, element);
        }

        /// <summary>
        /// Gibt einen visuellen Parent eines bestimmten Typs eines DependencyObjects zurück.
        /// </summary>
        /// <typeparam name="TParent">The type of the parent.</typeparam>
        /// <param name="dependencyObject">The dependency object.</param>
        /// <returns>the visual parent</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "but we want that generic method")]
        public static TParent GetVisualParent<TParent>(DependencyObject dependencyObject) where TParent : DependencyObject
        {
            return GetVisualParent<TParent>(dependencyObject, true);
        }

        /// <summary>
        /// Gibt einen visuellen Parent eines bestimmten Typs eines DependencyObjects zurück.
        /// </summary>
        /// <typeparam name="TParent">The type of the parent.</typeparam>
        /// <param name="dependencyObject">The dependency object.</param>
        /// <param name="walkthroughPopupRoot">if set to <c>true</c> [walkthrough popup root].</param>
        /// <returns>the visual parent</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "but we want that generic method")]
        public static TParent GetVisualParent<TParent>(DependencyObject dependencyObject, bool walkthroughPopupRoot) where TParent : DependencyObject
        {
            if (dependencyObject == null)
                return null;

            return GetVisualParent<TParent>(dependencyObject, walkthroughPopupRoot, parent => true);
        }

        /// <summary>
        /// Gibt einen visuellen Parent eines bestimmten Typs eines DependencyObjects zurück.
        /// </summary>
        /// <typeparam name="TParent">The type of the parent.</typeparam>
        /// <param name="dependencyObject">The dependency object.</param>
        /// <param name="walkthroughPopupRoot">if set to <c>true</c> [walkthrough popup root].</param>
        /// <param name="comparePredicate">Func mit dem Parent-Vergleich</param>
        /// <returns>the visual parent</returns>
        public static TParent GetVisualParent<TParent>(DependencyObject dependencyObject, bool walkthroughPopupRoot, Func<TParent, bool> comparePredicate)
            where TParent : DependencyObject
        {
            if (dependencyObject == null)
                return null;

            // Wenn es kein Visual (z.B. Run) oder ein PopupRoot ist, dann komme ich nur im LogicalTree weiter voran...
            DependencyObject parent = (!(dependencyObject is Visual) || (walkthroughPopupRoot && dependencyObject.GetType().Name == "PopupRoot")) ?
                LogicalTreeHelper.GetParent(dependencyObject) : VisualTreeHelper.GetParent(dependencyObject);
            if (parent != null && parent is TParent && comparePredicate((TParent)parent))
                return (TParent)parent;
            else
                return GetVisualParent<TParent>(parent, walkthroughPopupRoot, comparePredicate);
        }

        /// <summary>
        /// Finds the element under pointer.
        /// </summary>
        /// <typeparam name="T">type of FrameworkElement</typeparam>
        /// <param name="reference">The reference.</param>
        /// <param name="mousePosition">The mouse position.</param>
        /// <returns>the found element</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "but we want that generic method")]
        public static T FindElementUnderPointer<T>(Visual reference, Point mousePosition) where T : FrameworkElement
        {
            // Hit-test to find out the ItemsControl under the mouse-pointer.
            frameworkElement = null;
            hittedFrameworkElements = null;
            searchedElementType = typeof(T);
            VisualTreeHelper.HitTest(reference,
                null,
                new HitTestResultCallback(VisualHelper.TypeResultCallback),
                new PointHitTestParameters(mousePosition));

            try
            {
                return (T)frameworkElement;
            }
            finally
            {
                frameworkElement = null;
                hittedFrameworkElements = null;
                searchedElementType = null;
            }
        }

        /// <summary>
        /// Gets the elements under pointer.
        /// </summary>
        /// <param name="reference">The reference.</param>
        /// <param name="mousePosition">The mouse position.</param>
        /// <returns>the list of FrameworkElements</returns>
        public static IEnumerable<FrameworkElement> GetElementsUnderPointer(Visual reference, Point mousePosition)
        {
            // Hit-test to find out the ItemsControl under the mouse-pointer.
            frameworkElement = null;
            hittedFrameworkElements = new List<FrameworkElement>();
            searchedElementType = null;

            VisualTreeHelper.HitTest(reference,
                new HitTestFilterCallback(VisualHelper.NoFilterHitTestCallback),
                new HitTestResultCallback(VisualHelper.HitTestResultCallback),
                new PointHitTestParameters(mousePosition));

            try
            {
                return hittedFrameworkElements;
            }
            finally
            {
                frameworkElement = null;
                hittedFrameworkElements = null;
                searchedElementType = null;
            }
        }

        /// <summary>
        /// the needed result callback for the Hittest method.
        /// </summary>
        /// <param name="result">The result.</param>
        /// <returns>the HitTestResultBehavior</returns>
        private static HitTestResultBehavior TypeResultCallback(HitTestResult result)
        {
            Type t = result.VisualHit.GetType();

            if (t == searchedElementType)
            {
                frameworkElement = (FrameworkElement)result.VisualHit;
                return HitTestResultBehavior.Stop;
            }
            else if (t.IsSubclassOf(searchedElementType))
            {
                frameworkElement = (FrameworkElement)result.VisualHit;
                return HitTestResultBehavior.Stop;
            }

            frameworkElement = null;
            return HitTestResultBehavior.Continue;
        }

        /// <summary>
        /// the needed NoFilterHitTestCallback for the Hittest method.
        /// </summary>
        /// <param name="target">The target.</param>
        /// <returns>the HitTestFilterBehavior</returns>
        private static HitTestFilterBehavior NoFilterHitTestCallback(DependencyObject target)
        {
            var targetAsFrameworkElement = target as FrameworkElement;
            if (hittedFrameworkElements != null && targetAsFrameworkElement != null && targetAsFrameworkElement.IsVisible)
                hittedFrameworkElements.Add(targetAsFrameworkElement);
            return HitTestFilterBehavior.Continue;
        }

        /// <summary>
        /// the needed HitTestResultCallback for the Hittest method.
        /// </summary>
        /// <param name="result">The result.</param>
        /// <returns>the HitTestResultBehavior</returns>
        private static HitTestResultBehavior HitTestResultCallback(HitTestResult result)
        {
            return HitTestResultBehavior.Continue;
        }

        /// <summary>
        /// Does the tasks. 
        /// TODO : Not Needed?!?!
        /// </summary>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        public static bool DoTasks(Action action)
        {
            bool queryDone = false;
            // 1: do work on background task
            Task.Factory.StartNew(() => action())
                //schedule this task on the UI thread after the
                //background task completes
                .ContinueWith(
                    (task) =>
                    {
                        //must check for task fault so that exceptions
                        //are handled correctly.
                        //If task.Exception is not checked, then any
                        //exceptions from a background task will be
                        // rethrown in the task's finalizer
                        if (task.IsFaulted)
                        {
                            // MessageBox.Show(task.Exception.ToString());
                        }
                        queryDone = task.IsCompleted;
                    },
                //use the current synchronization context
                //as the task scheduler so that
                //continuation occurs on UI thread
                        TaskScheduler.FromCurrentSynchronizationContext());
            return queryDone;
        }

        public static void DoTasksParallel(params Action[] actions)
        {
            Parallel.Invoke(actions);

            //var tasks = new object[actions.Length];
            //int counter = 0;
            //actions.ForEach(a => tasks[counter++] = Task.Factory.StartNew(a));
            //// Problem: cannot cast object[] into Task[] => Contravarianz ??
            //Task.WaitAll((Task[])tasks);
        }

        /// <summary>
        /// Arbeitet alle Items im Dispatcher ab.
        /// </summary>
        public static void DoEvents()
        {
            if (Application.Current != null)
                Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new System.Action(() => { }));
        }
    }
}
